/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						     campaign_file_header.cpp

	$Header: /game/campaign_file_header.cpp $

	$NoKeywords: $

 ************************************************************************/

#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "campaign_file_header.h"

#include <assert.h>
#include <fstream>
#include <memory>
#include <string.h>

#include "campaign_file.h"
#include "campaign_file_ptr.h"
#include "campaign_file_ref.h"
#include "compressed_filter.h"
#include "elements_of.h"
#include "map_header.h"
#include "sized_int_types.h"
#include "streambuf_operators.h"

// --------------------------------------------------------------------------
// Unnamed namespace
// --------------------------------------------------------------------------

namespace
{

	int const k_current_file_version = 22;

	// --------------------------------------------------------------------------
	// t_campaign_file_opener class
	// --------------------------------------------------------------------------

	class t_campaign_file_opener : private t_campaign_file_ref::t_accessor
	{
	public:
		// Operator
		std::auto_ptr< std::streambuf > operator()(
			t_campaign_file_ref const & file_ref );

	private:
		// Static member functions
		static std::string	get_resource_name( t_standard_campaign_id	campaign_id );

		// Data members
		t_owned_ptr< std::streambuf >	m_result;

		// Member functions
		virtual void	access( std::string const & file_name );
		virtual void	access( t_standard_campaign_id campaign_id );
		void			open_file( std::string const & file_name );
		void			open_resource( std::string const & file_name );
	};

	// --------------------------------------------------------------------------
	// t_campaign_file_opener class
	// --------------------------------------------------------------------------

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	std::string t_campaign_file_opener::get_resource_name(
		t_standard_campaign_id	campaign_id )
	{
		std::string result;
	
		char const * file_name;

		switch ( campaign_id )
		{
		case k_standard_campaign_order:
			file_name = "campaign.order_campaign";
			break;
		case k_standard_campaign_might:
			file_name = "campaign.might_campaign";
			break;
		case k_standard_campaign_life:
			file_name = "campaign.life_campaign";
			break;
		case k_standard_campaign_death:
			file_name = "campaign.death_campaign";
			break;
		case k_standard_campaign_nature:
			file_name = "campaign.nature_campaign";
			break;
		case k_standard_campaign_chaos:
			file_name = "campaign.chaos_campaign";
			break;
		case k_standard_campaign_tutorial:
			file_name = "single.tutorial";
			break;
		default:
			assert( false );
		}

		result += file_name;
		return result;
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	std::auto_ptr< std::streambuf > t_campaign_file_opener::operator()(
		t_campaign_file_ref const & file_ref )
	{
		file_ref.accept( *this );
		std::auto_ptr< std::streambuf > result( m_result.release() );
		return result;
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	void t_campaign_file_opener::access( std::string const & file_name )
	{
		open_file( file_name );
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	void t_campaign_file_opener::access( t_standard_campaign_id campaign_id )
	{
		open_resource( get_resource_name( campaign_id ) );
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	void t_campaign_file_opener::open_file( std::string const & file_name )
	{
		t_owned_ptr< std::filebuf > file_ptr( new std::filebuf );
		if ( file_ptr->open( file_name.c_str(), std::ios::in | std::ios::binary ) == 0 )
			return;
		m_result.reset( file_ptr.release() );
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	void t_campaign_file_opener::open_resource( std::string const & file_name )
	{
		//retrive the resource from the resource file.

		t_campaign_file_cache					cache;
		t_campaign_file_ptr						data;
		
		cache = t_campaign_file_cache(file_name);
		data  = cache.get();

		if (data.get_cache() == NULL) 
			return ;
		
		t_memory_buffer_counted_ptr buffer = data->get_data_buffer();
		
		//this new memory buffer is put into m_result which is eventually passed
		//to an auto_ptr<> which then will either pass it onto a t_owned_ptr<> or
		//destroy it. 

		t_memory_buffer*  new_buffer = new t_memory_buffer(buffer);

		new_buffer->merge_buffers_for_read();

		m_result.reset( new_buffer );
	}
	// --------------------------------------------------------------------------
	// Non member functions
	// --------------------------------------------------------------------------
	bool read_signature_and_version( std::streambuf & stream, int & version )
	{
		static char const k_file_signature[] = "H4CAMPAIGN";

		char signature[ ELEMENTS_OF( k_file_signature ) - 1 ];
		if (	read( stream, signature, sizeof( signature ) ) != sizeof( signature )
			||	memcmp( signature, k_file_signature, sizeof( signature ) ) != 0 )
			return false;

		version = get< t_int32 >( stream );
		return version >= 21 && version <= k_current_file_version;
	}

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	template < typename t_file_header >
	t_counted_ptr< t_file_header > create_and_read(
		std::streambuf &		stream,
		t_progress_handler *	progress_handler_ptr )
	{
		t_counted_ptr< t_file_header > result( new t_file_header );
		if ( !result->read( stream, progress_handler_ptr ) )
			return t_counted_ptr< t_file_header >();
		return result;
	}

} // Unnamed namespace

// --------------------------------------------------------------------------
// t_campaign_file_header_visitor members
// --------------------------------------------------------------------------

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_campaign_file_header_visitor::visit(
	t_multi_scenario_campaign_file_header &	header )
{
	visit( const_cast< t_multi_scenario_campaign_file_header const & >( header ) );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_campaign_file_header_visitor::visit(
	t_multi_scenario_campaign_file_header const &	header )
{
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_campaign_file_header_visitor::visit(
	t_single_scenario_campaign_file_header &	header )
{
	visit( const_cast< t_single_scenario_campaign_file_header const & >( header ) );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_campaign_file_header_visitor::visit(
	t_single_scenario_campaign_file_header const &	header )
{
}

// --------------------------------------------------------------------------
// t_single_scenario_campaign_file_header members
// --------------------------------------------------------------------------

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_single_scenario_campaign_file_header::t_single_scenario_campaign_file_header()
	:	m_map_header_ptr( new t_map_header )
{
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_single_scenario_campaign_file_header::~t_single_scenario_campaign_file_header()
{
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_single_scenario_campaign_file_header::accept( t_visitor & visitor )
{
	visitor.visit( *this );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_single_scenario_campaign_file_header::accept( t_visitor & visitor ) const
{
	visitor.visit( *this );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
int t_single_scenario_campaign_file_header::get_map_count() const
{
	return 1;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
std::streambuf::pos_type
t_single_scenario_campaign_file_header::get_map_data_end(
	int	map_num ) const
{
	assert( map_num == 0 );
	return m_map_data_end;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
std::streambuf::pos_type
t_single_scenario_campaign_file_header::get_map_data_start(
	int	map_num ) const
{
	assert( map_num == 0 );
	return m_map_data_start;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_map_header const & t_single_scenario_campaign_file_header::get_map_header(
	int	map_num ) const
{
	assert( map_num == 0 );
	return *m_map_header_ptr;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
std::string const & t_single_scenario_campaign_file_header::get_name() const
{
	return m_map_header_ptr->name;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
bool t_single_scenario_campaign_file_header::is_multi_scenario() const
{
	return false;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
bool t_single_scenario_campaign_file_header::read(
	std::streambuf &		stream,
	t_progress_handler *	progress_handler_ptr )
{
	m_map_data_start = stream.pubseekoff( 0, std::ios::cur );
	m_map_data_end = stream.pubseekoff( 0, std::ios::end );
	stream.pubseekpos( m_map_data_start );

	try
	{
		t_inflate_filter inflater( stream );
		if ( !::read( inflater, *m_map_header_ptr, progress_handler_ptr ) ) 
			return false;
	}
	catch ( t_inflate_filter::t_data_error const & )
	{
		return false;
	}
	return true;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_single_scenario_campaign_file_header::set_map_header(
	t_map_header const &	new_map_header )
{
	*m_map_header_ptr = new_map_header;
}

// --------------------------------------------------------------------------
// t_multi_scenario_campaign_file_header members
// --------------------------------------------------------------------------

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_multi_scenario_campaign_file_header::~t_multi_scenario_campaign_file_header()
{
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_multi_scenario_campaign_file_header::accept( t_visitor & visitor )
{
	visitor.visit( *this );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_multi_scenario_campaign_file_header::accept( t_visitor & visitor ) const
{
	visitor.visit( *this );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
int t_multi_scenario_campaign_file_header::get_map_count() const
{
	return m_map_info_vector.size();
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
std::streambuf::pos_type t_multi_scenario_campaign_file_header::get_map_data_end(
	int	map_num ) const
{
	assert( map_num >= 0 && map_num < m_map_info_vector.size() );
	return m_map_info_vector[ map_num ].data_end;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
std::streambuf::pos_type t_multi_scenario_campaign_file_header::get_map_data_start(
	int	map_num ) const
{
	assert( map_num >= 0 && map_num < m_map_info_vector.size() );
	return m_map_info_vector[ map_num ].data_start;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_map_header const & t_multi_scenario_campaign_file_header::get_map_header(
	int	map_num ) const
{
	assert( map_num >= 0 && map_num < m_map_info_vector.size() );
	return *m_map_info_vector[ map_num ].header_ptr;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
std::string const & t_multi_scenario_campaign_file_header::get_name() const
{
	return m_name;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
std::string const &
t_multi_scenario_campaign_file_header::get_description() const
{
	return m_description;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
bool t_multi_scenario_campaign_file_header::is_multi_scenario() const
{
	return true;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
bool t_multi_scenario_campaign_file_header::read(
	std::streambuf &		stream,
	t_progress_handler *	progress_handler_ptr )
{
	int const k_current_format_version = 1;

	int format_version = get< t_uint16 >( stream );
	if ( format_version < 0 || format_version > k_current_format_version )
		return false;

	std::string new_name;
	std::string new_description;
	if (	!read_string16( stream, new_name )
		||	( format_version >= 1 && !read_string16( stream, new_description ) ) )
		return false;

	int map_count = get< t_uint16 >( stream );
	if ( map_count <= 0 )
		return false;

	std::vector< std::streambuf::off_type > map_data_sizes( map_count );
	std::vector< t_map_info > new_map_info_vector( map_count );

	int map_num;
	for ( map_num = 0; map_num < map_count; ++map_num )
		map_data_sizes[ map_num ] = get< t_uint32 >( stream );

	std::streambuf::pos_type start_pos = stream.pubseekoff( 0, std::ios::cur );
	map_num = 0;
	while( true )
	{
		std::streambuf::pos_type end_pos = start_pos + map_data_sizes[ map_num ];

		t_map_info & map_info = new_map_info_vector[ map_num ];
		map_info.data_start = start_pos;
		map_info.data_end = end_pos;
		map_info.header_ptr = new t_map_header;

		stream.pubseekpos( start_pos );
		try
		{
			t_inflate_filter inflater( stream );
			if ( !::read( inflater, *map_info.header_ptr, progress_handler_ptr ) ) 
				return false;
		}
		catch ( t_inflate_filter::t_data_error const & )
		{
			return false;
		}

		if ( ++map_num >= map_count )
			break;

		start_pos = end_pos;
	}

	m_name.swap( new_name );
	m_description.swap( new_description );
	m_map_info_vector.swap( new_map_info_vector );

	return true;
}

// --------------------------------------------------------------------------
// Non-member functions
// --------------------------------------------------------------------------

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_campaign_file_header_ptr create_campaign_file_header(
	t_campaign_file_ref const &	file_ref,
	t_progress_handler *		progress_handler_ptr )
{
	t_owned_ptr< std::streambuf > stream_ptr = open_campaign_file( file_ref );
	if ( stream_ptr.get() == 0 )
		return t_campaign_file_header_ptr();

	int version;
	if ( !read_signature_and_version( *stream_ptr, version ) )
		return t_campaign_file_header_ptr();

	bool multi_scenario = get< t_uint8 >( *stream_ptr ) != 0;
	if ( multi_scenario )
	{
		return	create_and_read< t_multi_scenario_campaign_file_header >(
					*stream_ptr, progress_handler_ptr );
	}

	return	create_and_read< t_single_scenario_campaign_file_header >(
				*stream_ptr, progress_handler_ptr );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_single_scenario_campaign_file_header_ptr
create_single_scenario_campaign_file_header(
	t_campaign_file_ref const &	file_ref,
	t_progress_handler *		progress_handler_ptr )
{
	t_owned_ptr< std::streambuf > stream_ptr = open_campaign_file( file_ref );
	if ( stream_ptr.get() == 0 )
		return t_single_scenario_campaign_file_header_ptr();

	int version;
	if ( !read_signature_and_version( *stream_ptr, version ) )
		return t_single_scenario_campaign_file_header_ptr();

	bool multi_scenario = get< t_uint8 >( *stream_ptr ) != 0;
	if ( multi_scenario )
		return t_single_scenario_campaign_file_header_ptr();

	return	create_and_read< t_single_scenario_campaign_file_header >(
				*stream_ptr, progress_handler_ptr );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_multi_scenario_campaign_file_header_ptr
create_multi_scenario_campaign_file_header(
	t_campaign_file_ref const &	file_ref,
	t_progress_handler *		progress_handler_ptr )
{
	t_owned_ptr< std::streambuf > stream_ptr = open_campaign_file( file_ref );
	if ( stream_ptr.get() == 0 )
		return t_multi_scenario_campaign_file_header_ptr();

	int version;
	if ( !read_signature_and_version( *stream_ptr, version ) )
		return t_multi_scenario_campaign_file_header_ptr();

	bool multi_scenario = get< t_uint8 >( *stream_ptr ) != 0;
	if ( !multi_scenario )
		return t_multi_scenario_campaign_file_header_ptr();

	return	create_and_read< t_multi_scenario_campaign_file_header >(
				*stream_ptr, progress_handler_ptr );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
std::auto_ptr< std::streambuf > open_campaign_file(
	t_campaign_file_ref const &	file_ref )
{
	return t_campaign_file_opener()( file_ref );
}
